package com.chase.passwordmaster.encoder.encoder_v2

import android.util.Base64
import android.util.Log
import com.chase.passwordmaster.data.GlobalData
import com.chase.passwordmaster.data.refreshGlobalData
import com.chase.passwordmaster.encoder.EncodeExceptionCode
import com.chase.passwordmaster.encoder.Encoder
import com.chase.passwordmaster.encoder.EncoderException
import com.chase.passwordmaster.encoder.EncoderRefreshInfo
import com.chase.passwordmaster.encoder.encoder_v2.EncoderV2.toDecryptedPrivateKey
import com.chase.passwordmaster.encoder.encoder_v2.EncoderV2.toEncryptedPrivateKey
import com.chase.passwordmaster.entity.RSAKeyPair
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher

/**
 * Content:
 * Typde | Method | Method
 * String | ↓ toByteArray() | Display
 * ByteArray | ↓ cipher.doFinal() | ↑ String()
 * ByteArray | ↓ encryptBASE64() | ↑ cipher.doFinal()
 * String | Save in Data Base | ↑ decryptBASE64()
 * OR
 * Typde | Method | Method
 * String | ↓ toByteArray() | Display
 * ByteArray | ↓ rsaEncrypted(publicKey) | ↑ String()
 * ByteArray | ↓ base64Encrypted() | ↑ rsaDecrypted(privateKey)
 * String | Save in Data Base | ↑ base64Encrypted()
 * -----------------------------------------------
 * PrivateKey
 * Type | Method
 * String | ↓ getDecryptedPrivateKeyByLoginPass() or toDecryptedPrivateKey(loginPass)
 * String | ↓ decryptBASE64() or base64Encrypted()
 * ByteArray | ↓ toPrivateKey()
 * PrivateKey | For decrypted
 */

object EncoderV2 : Encoder {
    private const val KEY_ALGORITHM = "RSA"
    private const val RSA = "RSA"
    private const val PREFIX_LENGTH = 2
    private const val PREFIX = "V2"

    private var charMap: Map<Char, Char>? = null
    private var reversedCharMap: Map<Char, Char>? = null

    /**
     * @return KeyPair
     */
    private fun generateKeyPairRaw(size: Int = 1024) = KeyPairGenerator.getInstance(KEY_ALGORITHM)
        .also { it.initialize(size) }
        .genKeyPair()

    /**
     * @return Pair<String, String>
     */
    fun generateKeyPairInString(size: Int = 1024): Pair<String, String> {
        val keyPair = generateKeyPairRaw(size)
        return Pair(
            keyPair.public.encoded.base64Encrypted(),
            keyPair.private.encoded.base64Encrypted()
        )
    }

    /**
     * Decrypt BASE64 into ByteArray
     */
    fun String.base64Decrypted(): ByteArray = Base64.decode(this, Base64.DEFAULT)

    @Deprecated(
        "推荐使用扩展函数防止嵌套逻辑不清晰",
        ReplaceWith("key.base64Decrypted()")
    )
    fun decryptBASE64(key: String) = key.base64Decrypted()

    /**
     * Encrypt ByteArray to String
     */
    fun ByteArray.base64Encrypted(): String = Base64.encodeToString(this, Base64.DEFAULT)

    @Deprecated(
        "推荐使用扩展函数防止嵌套逻辑不清晰",
        ReplaceWith("key.base64Encrypted()")
    )
    fun encryptBASE64(key: ByteArray) = key.base64Encrypted()

    /**
     * 通过公钥byte[](publicKey.getEncoded())将公钥还原，适用于RSA算法
     *
     * @receiver ByteArray
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
    fun ByteArray.toPublicKey(): PublicKey {
        val keySpec = X509EncodedKeySpec(this)
        val keyFactory = KeyFactory.getInstance(RSA)
        return keyFactory.generatePublic(keySpec)
    }

    /**
     * 通过私钥byte[]将公钥还原，适用于RSA算法
     *
     * @receiver ByteArray
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
    fun ByteArray.toPrivateKey(): PrivateKey {
        val keySpec = PKCS8EncodedKeySpec(this)
        val keyFactory = KeyFactory.getInstance(RSA)
        return keyFactory.generatePrivate(keySpec)
    }

    /**
     * Convert ByteArray into PublicKey
     *
     * @return PublicKey
     */
    fun String.toPublicKey() = this.base64Decrypted().toPublicKey()

    /**
     * Convert ByteArray into PrivateKey
     *
     * @return PrivateKey
     */
    fun String.toPrivateKey() = this.base64Decrypted().toPrivateKey()

    @Deprecated(
        "推荐使用扩展函数防止嵌套过多逻辑不清晰",
        ReplaceWith("privateKey.toEncryptedPrivateKey(loginPass)")
    )
    fun getEncryptedPrivateKeyByLoginPass(privateKey: String, loginPass: String) =
        privateKey.toEncryptedPrivateKey(loginPass)

    /**
     * Encrypt private key with login pass
     *
     * @return encrypted private key
     */
    fun String.toEncryptedPrivateKey(loginPass: String): String {
        // 日后修改成 native 方法编译成二进制
        // 先使用简单的算法

        fun encrypt(privateKey: String, charMap: Map<Char, Char>): String {
            val encryptedPrivateKey = StringBuilder()
            privateKey.forEach {
                if (it == '\n') {
                    encryptedPrivateKey.append(it)
                } else {
                    encryptedPrivateKey.append(charMap[it])
                }
            }
            return encryptedPrivateKey.toString()
        }

        charMap?.let {
            return encrypt(this, it)
        } ?: run {
            val gap = loginPass.length + loginPass[0].toInt()
            charMap = generateCharMap(gap)
            charMap?.let { return encrypt(this@toEncryptedPrivateKey, it) }
                ?: throw Exception("charMap is null")
        }
    }

    @Deprecated(
        "推荐使用扩展函数防止嵌套过多逻辑不清晰",
        ReplaceWith("encryptedPrivateKey.toDecryptedPrivateKey(loginPass)")
    )
    fun getDecryptedPrivateKeyByLoginPass(encryptedPrivateKey: String, loginPass: String) =
        encryptedPrivateKey.toDecryptedPrivateKey(loginPass)

    /**
     * Decrypt private key with login pass
     *
     * @return decrypted private key
     */
    fun String.toDecryptedPrivateKey(loginPass: String): String {
        fun decrypt(encryptedPrivateKey: String, reversedCharMap: Map<Char, Char>): String {
            val privateKey = StringBuilder()
            encryptedPrivateKey.forEach {
                if (it == '\n') {
                    privateKey.append(it)
                } else {
                    privateKey.append(reversedCharMap[it])
                }
            }
            return privateKey.toString()
        }

        reversedCharMap?.let {
            return decrypt(this, it)
        } ?: run {
            val gap = loginPass.length + loginPass[0].toInt()
            reversedCharMap = generateCharMapReverse(gap)
            reversedCharMap?.let {
                return decrypt(this@toDecryptedPrivateKey, it)
            } ?: throw Exception("reversedCharMap is null")
        }
    }

    /**
     * Encrypt content format in ByteArray
     */
    private fun ByteArray.rsaEncrypted(publicKey: PublicKey): ByteArray {
        val cipher = Cipher.getInstance(RSA)
        cipher.init(Cipher.ENCRYPT_MODE, publicKey)
        return cipher.doFinal(this)
    }

    /**
     * Decrypt content format in ByteArray
     */
    private fun ByteArray.rsaDecrypted(privateKey: PrivateKey): ByteArray {
        val cipher = Cipher.getInstance(RSA)
        cipher.init(Cipher.DECRYPT_MODE, privateKey)
        return cipher.doFinal(this)
    }

    /**
     * Encrypt content by RSA
     * SERVEICE
     */
    fun encryptByRSA(content: String, publicKey: String): String =
        content.toByteArray().rsaEncrypted(publicKey.toPublicKey()).base64Encrypted()

    /**
     * Decrypt content by RSA
     * SERVEICE
     */
    fun decryptByRSA(content: String, encryptedPrivateKey: String, loginPass: String): String =
        String(
            content.base64Decrypted().rsaDecrypted(
                encryptedPrivateKey.toDecryptedPrivateKey(loginPass).toPrivateKey()
            )
        )

    const val MAP_START = 33
    const val MAP_END = 126

    /**
     * Generate char map
     */
    fun generateCharMap(gap: Int, start: Int = MAP_START, end: Int = MAP_END): Map<Char, Char> {
        val g = gap % ((end - start) / 3)

        val result = HashMap<Char, Char>()

        for (keyInt in start..end) {
            val key = keyInt.toChar()
            var valueInt = keyInt + g
            if (valueInt > end) {
                valueInt = valueInt - end + start - 1
            }
            val value = valueInt.toChar()
            result[key] = value
        }

        return result
    }

    /**
     * Generate reversed char map
     */
    fun generateCharMapReverse(
        gap: Int,
        start: Int = MAP_START,
        end: Int = MAP_END
    ): Map<Char, Char> {
        val g = gap % ((end - start) / 3)

        val result = HashMap<Char, Char>()

        for (keyInt in start..end) {
            val key = keyInt.toChar()
            var valueInt = keyInt - g
            if (valueInt < start) {
                valueInt = end - (start - valueInt) + 1
            }
            val value = valueInt.toChar()
            result[key] = value
        }

        return result
    }

    /**
     * Add prefix
     */
    private fun String.addPrefix(prefix: String = PREFIX) = "$prefix$this"

    /**
     * Remove prefix
     */
    private fun String.removePrefix(prefixLength: Int = PREFIX_LENGTH) =
        this.substring(prefixLength)

    override fun match(content: String) = PREFIX == content.substring(0, PREFIX_LENGTH)

    override fun encode(content: String): String {
        GlobalData.rsaKeyPairInString?.let {
            return encryptByRSA(content, it.publicKey).addPrefix()
        } ?: run {
            Log.i("GSC:", "EncoderV2.encode:V2_NO_GLOBAL_DATA_RSA_KEY_PAIR")
            throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_RSA_KEY_PAIR)
        }
    }

    override fun decode(content: String): String {
        GlobalData.rsaKeyPairInString?.let {
            GlobalData.loginPassword?.run {
                return decryptByRSA(content.removePrefix(), it.privateKey, this)
            } ?: throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_LOGIN_PASSWORD)
        } ?: throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_RSA_KEY_PAIR)
    }

    private fun refreshCharMaps(loginPass: String) {
        val gap = loginPass.length + loginPass[0].toInt()
        charMap = generateCharMap(gap)
        reversedCharMap = generateCharMapReverse(gap)
    }

    override fun refresh(encoderRefreshInfo: EncoderRefreshInfo) {
        refreshAndSaveRSAKeyByLoginPass(encoderRefreshInfo.oldLoginPass, encoderRefreshInfo.newLoginPass)
        refreshCharMaps(encoderRefreshInfo.newLoginPass)
    }
}

/**
 * Generate and save RSA key pair by login pass
 * Used when first set login pass or change login pass
 *
 * @return success?
 */
fun generateAndSaveRSAKeyByLoginPass(loginPass: String, rsaKeySize: Int = 1024) =
    if (GlobalData.rsaKeyPairInString == null) {
        val keyPairInString = EncoderV2.generateKeyPairInString(rsaKeySize)
        RSAKeyPair(
            keyPairInString.first,
            keyPairInString.second.toEncryptedPrivateKey(loginPass)
        ).save()
        refreshGlobalData()
        true
    } else {
        false
    }

/**
 * Refresh and save RSA key pair by login pass
 * Used when login pass being reset
 *
 * @return success?
 */
fun refreshAndSaveRSAKeyByLoginPass(oldLoginPass: String, newLoginPass: String) =
    GlobalData.rsaKeyPairInString?.let {
        it.privateKey = it.privateKey
            .toDecryptedPrivateKey(oldLoginPass)
            .toEncryptedPrivateKey(newLoginPass)
        it.save()
    } ?: false

// package com.chase.passwordmaster.encoder.encoder_v2

// import android.util.Base64
// import android.util.Log
// import com.chase.passwordmaster.data.GlobalData
// import com.chase.passwordmaster.data.initGlobalData
// import com.chase.passwordmaster.data.refreshGlobalData
// import com.chase.passwordmaster.encoder.EncodeExceptionCode
// import com.chase.passwordmaster.encoder.Encoder
// import com.chase.passwordmaster.encoder.EncoderException
// import com.chase.passwordmaster.entity.RSAKeyPair
// import java.security.*
// import java.security.spec.InvalidKeySpecException
// import java.security.spec.PKCS8EncodedKeySpec
// import java.security.spec.X509EncodedKeySpec
// import javax.crypto.Cipher
// import kotlin.math.min

// object EncoderV2 : Encoder {
//     private const val KEY_ALGORITHM = "RSA"
//     private const val RSA = "RSA"
//     private const val PREFIX_LENGTH = 2
//     private const val PREFIX = "V2"

//     private var charMap: Map<Char, Char>? = null
//     private var reversedCharMap: Map<Char, Char>? = null

//     /**
//      * Decrypt BASE64 into ByteArray
//      */
//     fun decryptBASE64(key: String) = Base64.decode(key, Base64.DEFAULT)

//     /**
//      * Encrypt ByteArray to String
//      */
//     fun encryptBASE64(key: ByteArray) = Base64.encodeToString(key, Base64.DEFAULT)


//     /**
//      * @return KeyPair
//      */
//     private fun generateKeyPairRaw(size: Int = 1024) = KeyPairGenerator.getInstance(KEY_ALGORITHM)
//         .also { it.initialize(size) }
//         .genKeyPair()

//     /**
//      * @return Pair<String, String>
//      */
//     fun generateKeyPairInString(size: Int = 1024): Pair<String, String> {
//         val keyPair = generateKeyPairRaw(size)
//         return Pair(encryptBASE64(keyPair.public.encoded), encryptBASE64(keyPair.private.encoded))
//     }

//     /**
//      * 通过公钥byte[](publicKey.getEncoded())将公钥还原，适用于RSA算法
//      *
//      * @param keyBytes
//      * @return
//      * @throws NoSuchAlgorithmException
//      * @throws InvalidKeySpecException
//      */
//     @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
//     fun ByteArray.toPublicKey(): PublicKey {
//         val keySpec = X509EncodedKeySpec(this)
//         val keyFactory = KeyFactory.getInstance(RSA)
//         return keyFactory.generatePublic(keySpec)
//     }

//     /**
//      * 通过私钥byte[]将公钥还原，适用于RSA算法
//      *
//      * @return
//      * @throws NoSuchAlgorithmException
//      * @throws InvalidKeySpecException
//      */
//     @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
//     fun ByteArray.toPrivateKey(): PrivateKey {
//         val keySpec = PKCS8EncodedKeySpec(this)
//         val keyFactory = KeyFactory.getInstance(RSA)
//         return keyFactory.generatePrivate(keySpec)
//     }

//     /**
//      * Convert ByteArray into PublicKey
//      * @return PublicKey
//      */
//     fun String.toPublicKey() = decryptBASE64(this).toPublicKey()

//     /**
//      * Convert ByteArray into PrivateKey
//      * @return PrivateKey
//      */
//     fun String.toPrivateKey() = decryptBASE64(this).toPrivateKey()


//     /**
//      * Encrypt private key with login pass
//      * @return encrypted private key
//      */
//     fun getEncryptedPrivateKeyByLoginPass(privateKey: String, loginPass: String): String {
//         // 日后修改成 native 方法编译成二进制
//         // 先使用简单的算法

//         fun encrypt(privateKey: String, charMap: Map<Char, Char>): String {
//             val encryptedPrivateKey = StringBuilder()
//             privateKey.forEach {
//                 if (it == '\n') {
//                     encryptedPrivateKey.append(it)
//                 } else {
//                     encryptedPrivateKey.append(charMap[it])
//                 }
//             }
//             return encryptedPrivateKey.toString()
//         }

//         charMap?.let {
//             return encrypt(privateKey, it)
//         }?: run {
//             charMap = generateCharMap(loginPass.length)
//             charMap?.let { return encrypt(privateKey, it) }
//                 ?: throw Exception("charMap is null")
//          }
//     }

//     /**
//      * Decrypt private key with login pass
//      * @return decrypted private key
//      */
//     fun getDecryptedPrivateKeyByLoginPass(
//         encryptedPrivateKey: String,
//         loginPass: String
//     ): String {
//         fun decrypt(encryptedPrivateKey: String, reversedCharMap: Map<Char, Char>): String {
//             val privateKey = StringBuilder()
//             encryptedPrivateKey.forEach {
//                 if (it == '\n') {
//                     privateKey.append(it)
//                 } else {
//                     privateKey.append(reversedCharMap[it])
//                 }
//             }
//             return privateKey.toString()
//         }

//         reversedCharMap?.let {
//             return decrypt(encryptedPrivateKey, it)
//         }?: run {
//             reversedCharMap = generateCharMapReverse(loginPass.length)
//             reversedCharMap?.let {
//                 return decrypt(encryptedPrivateKey, it)
//             }?: throw Exception("reversedCharMap is null")
//          }
//     }

//     /**
//      * Encrypt content by RSA
//      */
//     fun encryptByRSA(content: String, publicKey: String) =
//         encryptByRSA(content, decryptBASE64(publicKey).toPublicKey())

//     fun encryptByRSA(
//         content: String,
//         publicKey: PublicKey
//     ) = encryptByRSA(content.toByteArray(), publicKey)

//     fun encryptByRSA(
//         content: ByteArray,
//         publicKey: PublicKey
//     ): String {
//         val cipher = Cipher.getInstance(RSA)
//         cipher.init(Cipher.ENCRYPT_MODE, publicKey)
//         return encryptBASE64(cipher.doFinal(content))
//     }


//     /**
//      * Decrypt content by RSA
//      */
//     fun decryptByRSA(content: String, privateKey: String, loginPass: String): String {
//         // val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
//         val cipher = Cipher.getInstance("RSA")
//         val decryptedPrivateKey =
//             decryptBASE64(getDecryptedPrivateKeyByLoginPass(privateKey, loginPass)).toPrivateKey()
//         cipher.init(Cipher.DECRYPT_MODE, decryptedPrivateKey)
//         return String(cipher.doFinal(decryptBASE64(content)))
//     }

//     @Deprecated(
//         "参数设计不便捷",
//         ReplaceWith("decryptByRSA(content.toByteArray(), privateKey, loginPass)")
//     )
//     fun decryptByRSA(content: String, privateKey: PrivateKey, loginPass: String) =
//         decryptByRSA(content.toByteArray(), privateKey, loginPass)

//     @Deprecated(
//         "参数设计不便捷",
//         ReplaceWith("decryptByRSA(String(content), encryptBASE64(privateKey.encoded), loginPass)")
//     )
//     fun decryptByRSA(content: ByteArray, privateKey: PrivateKey, loginPass: String): String {
//         val cipher = Cipher.getInstance("RSA")
//         val decryptedPrivateKey = decryptBASE64(
//             getDecryptedPrivateKeyByLoginPass(
//                 encryptBASE64(privateKey.encoded),
//                 loginPass
//             )
//         ).toPrivateKey()
//         cipher.init(Cipher.DECRYPT_MODE, decryptedPrivateKey)
//         return String(cipher.doFinal(content))
//     }

//     const val MAP_START = 33
//     const val MAP_END = 126

//     /**
//      * Generate char map
//      */
//     fun generateCharMap(gap: Int, start: Int = MAP_START, end: Int = MAP_END): Map<Char, Char> {
//         val g = gap % ((end - start) / 3)

//         val result = HashMap<Char, Char>()

//         for (keyInt in start..end) {
//             val key = keyInt.toChar()
//             var valueInt = keyInt + g
//             if (valueInt > end) {
//                 valueInt = valueInt - end + start - 1
//             }
//             val value = valueInt.toChar()
//             result[key] = value
//         }

//         return result
//     }

//     /**
//      * Generate reversed char map
//      */
//     fun generateCharMapReverse(gap: Int, start: Int = MAP_START, end: Int = MAP_END): Map<Char, Char> {
//         val g = gap % ((end - start) / 3)

//         val result = HashMap<Char, Char>()

//         for (keyInt in start..end) {
//             val key = keyInt.toChar()
//             var valueInt = keyInt - g
//             if (valueInt < start) {
//                 valueInt = end - (start - valueInt) + 1
//             }
//             val value = valueInt.toChar()
//             result[key] = value
//         }

//         return result
//     }

//     /**
//      * Add prefix
//      */
//     private fun String.addPrefix(prefix: String = PREFIX) = "$prefix$this"

//     /**
//      * Remove prefix
//      */
//     private fun String.removePrefix(prefixLength: Int = PREFIX_LENGTH) =
//         this.substring(prefixLength)

//     override fun match(content: String) = PREFIX == content.substring(0, PREFIX_LENGTH)

//     override fun encode(content: String): String {
//         GlobalData.rsaKeyPairInString?.let {
//             return encryptByRSA(content, it.publicKey).addPrefix()
//         } ?: run {
//             Log.i("GSC:", "EncoderV2.encode:V2_NO_GLOBAL_DATA_RSA_KEY_PAIR")
//             Log.d("GSC:", GlobalData.rsaKeyPairInString?.publicKey)
//             Log.d("GSC:", GlobalData.rsaKeyPairInString?.privateKey)
//             throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_RSA_KEY_PAIR)
//         }
//     }

//     override fun decode(content: String): String {
//         GlobalData.rsaKeyPairInString?.let {
//             GlobalData.loginPassword?.run {
//                 return decryptByRSA(content.removePrefix(), it.privateKey, this)
//             } ?: throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_LOGIN_PASSWORD)
//         } ?: throw EncoderException(EncodeExceptionCode.V2_NO_GLOBAL_DATA_RSA_KEY_PAIR)
//     }
// }

// /**
//  * Generate and save RSA key pair by login pass
//  * Used when first set login pass or change login pass
//  * @return success?
//  */
// fun generateAndSaveRSAKeyByLoginPass(loginPass: String, rsaKeySize: Int = 1024) =
//     if (GlobalData.rsaKeyPairInString == null) {
//         val keyPairInString = EncoderV2.generateKeyPairInString(rsaKeySize)
//         RSAKeyPair(
//             keyPairInString.first,
//             EncoderV2.getEncryptedPrivateKeyByLoginPass(keyPairInString.second, loginPass)
//         ).save()
//         refreshGlobalData()
//         true
//     } else {
//         false
//     }

// /**
//  * Refresh and save RSA key pair by login pass
//  * Used when login pass being reset
//  * @return success?
//  */
// fun refreshAndSaveRSAKeyByLoginPass(loginPass: String) =
//     GlobalData.rsaKeyPairInString?.let {
//         it.privateKey = EncoderV2.getEncryptedPrivateKeyByLoginPass(
//             EncoderV2.getDecryptedPrivateKeyByLoginPass(
//                 it.privateKey,
//                 loginPass
//             ), loginPass
//         )
//         true
//     } ?: false